ft_react is a lightweight, learning-focused implementation of the React library. It replicates core React features such as hooks, context, and routing โ and adds a few custom ones too.
- Motivation
- Features
- Installation
- Usage
- Tailwind CSS Integration
- Example
- Known Bugs
- Contributing
- License
The idea for this project came from my final project at 42 coding school (ft_transcendence), where using React was not allowed.
So I decided to write my own React-like implementation from scratch.
Check out the live demo of ft_react:
๐ https://ft-react.vercel.app/
This page demonstrates how the library works with routing, hooks, and custom features in action.
- Routing: Navigate between views without reloading the page.
- Hooks: Includes
useState,useStatic,useEffect,useRef,useContext,useNavigate, anduseSyncExternalStore. - Context API: Basic support for global state using providers.
useStatic is a custom hook I always wanted in React. It behaves like useState, except its state persists across the entire application and is shared between components automatically โ no need for context providers.
import React, { useStatic } from "react";
function AnotherComponent() {
const [test, setTest] = useStatic("simple", 0);
return (
<div>
<p>Value test in another component: {test}</p>
</div>
);
}
export function StaticStateSimple() {
const [test, setTest] = useStatic("simple", 0);
return (
<div>
<p>Simple static test: {test}</p>
<AnotherComponent />
<button onClick={() => setTest((prev) => prev + 1)}>Click</button>
</div>
);
}The "simple" key ensures the value persists even after component unmount, and syncs between components.
useLocalStorage is a custom hook that allows you to store and retrieve values from the browser's local storage.
import React, { useLocalStorage } from "react";
function App() {
const [name, setName] = useLocalStorage("name", "Anonymous");
return (
<div>
<h1>Hello, {name}!</h1>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}useLocalStorage is built on top of the useStatic, so the value persists after unmounts and changes will rerender all subscribed components.
To get started with ft_react, clone the repository and install the dependencies:
git clone https://github.com/emsa001/ft_react.git
cd ft_react
npm installTo start the development server:
npm run devYou can integrate Tailwind CSS with PostCSS using either v3 or v4:
export default {
plugins: {
tailwindcss: {}, // or "@tailwindcss/postcss": {}, for Tailwind v4
autoprefixer: {},
},
};/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{ts,tsx,html}"],
theme: {
extend: {},
},
plugins: [],
};@tailwind base;
@tailwind components;
@tailwind utilities;@layer theme, base, components, utilities;
@import "tailwindcss";
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);import React, { useState, useEffect, setTitle } from 'react';
const App = () => {
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>("Anonymous");
useEffect(() => {
setTitle(`Hello, ${name}!`);
}, [name]);
return (
<div>
<h1>Hello, {name}!</h1>
<p>Number: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
<input value={name} onChange={(e: any) => setName(e.target.value)} />
</div>
);
};
export default App;<BrowserRouter>
<Router src="/" component={<Home />} />
<Router src="/404" component={<NotFound />} default />
</BrowserRouter>-
Props with heavy objects
In some cases, when passing large or complex objects as props, updates may not propagate correctly to child components.
-
Component return value
Every component must return a valid HTML element. For example:
// Correct return ( <div> <MyComponent /> </div> ); // Incorrect return <MyComponent />;
Components cannot return
null. Instead, use:return <div />;
When using loops (e.g.,
.map) in JSX, ensure each item returns a valid HTML element:return ( <div> {items.map(item => ( <span key={item.id}>{item.value}</span> ))} </div> );
-
useStatic update scheduling
When updating a state with
useStatic, all other scheduled updates (such as normaluseState) are cancelled for that render cycle. For example:setUser(user); // setUser is a state from useStatic() setWindow(null); // normal useState update will be ignored
This can be easily fixed by splitting the updates into separate functions or using
setTimeout, or executing the normal state updates before theuseStaticupdate:setWindow(null); setUser(user);
Contributions are welcome!
Feel free to open an issue or submit a pull request for improvements or bug fixes.
This project is licensed under the MIT License.