Create Dynamic Components - reactjs

I want to dynamically create a component, when I implement something like this:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = () => {
const Comp = gen_Comp("Hello");
return (
<Comp txt="World" />
);
}
Something goes wrong (what exactly goes wrong is hard to explain because it's specific to my app, point is that I must be doing something wrong, because I seem to be losing state as my component gets rerendered). I also tried this with React.createElement, but the problem remains.
So, what is the proper way to create components at runtime?

The main way that react tells whether it needs to mount/unmount components is by the component type (the second way is keys). Every time App renders, you call gen_Comp and create a new type of component. It may have the same functionality as the previous one, but it's a new component and so react is forced to unmount the instance of the old component type and mount one of the new type.
You need to create your component types just once. If you can, i recommend you use your factory outside of rendering, so it runs just when the module loads:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const Comp = gen_Comp("Hello");
const App = () => {
return (
<Comp txt="World" />
);
}
If it absolutely needs to be done inside the rendering of a component (say, it depends on props), then you will need to memoize it:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = ({ spec }) => {
const Comp = useMemo(() => {
return gen_Comp(spec);
}, [spec]);
return (
<Comp txt="World" />
);
}

Related

Returning a function vs setting a const equal to a function in React

I'm relatively new to React and was wondering why I'm getting a Functions are not valid as a React child. This may happen if you return a Component instead of from render error.
In my code there is an existing:
const SomeConst = SomeFunctionThatReturnsAComponent();
Now I am trying to update that const to have some logic before calling the function like so:
const SomeConst = (props) => {
//Some logic
return (SomeFunctionThatReturnsAComponent()());
}
Here is SomeFunctionThatReturnsAComponent():
const Template = component => (props) => {
const ReturnThisComponent = props => <Component component={component}, {props} />
return ReturnThisComponent;
}
The usage of SomeConst remains the same as the original implementation:
const SomeComponent = (props) => {
return (<SomeConst prop1="foo"/>)
}
I am wondering why this is not functionally the same thing and why I am getting this error. I have not been able to find a post that gets this error with the way I have it implemented. They are different implementations that arrive at this same error so they were not that much of a help to me.
If there is another post, I would kindly ask to link me to it and I will quickly remove this post and link it to the other post for future people.
Please let me know if I can clarify anything, I have tried to create this post as a repex.
tl;dr - you are missing one function call.
SomeFunctionThatReturnsAComponent()();
what does each parentheses do?
The first parens are calling the first function, that returns a function that returns ReturnThisComponent. If you call this function (Template) only once, you will still return a function (props) => { ... };
const Template = component => (props) => {
const ReturnThisComponent = props => <Component component={component}, {props} />
return ReturnThisComponent;
}
So either - call the function twice, or - remove the second curried function (props) => (and I think this is what you wanted to achieve). So it will look like:
const Template = (component) => {
const Component = component;
const ReturnThisComponent = (props) => <Component {...props} />;
return ReturnThisComponent;
}

Making the state of a component affect the rendering of a sibling when components are rendered iteratively

I have the following code:
export default function Parent() {
const children1 = someArrayWithSeveralElements.map(foo => <SomeView />);
const children2 = someArrayWithSeveralElements.map(foo => <SomeCheckbox />);
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
For a given element foo, there is a SomeView component that is conditionally rendered based on the state of a SomeCheckbox. I'm having trouble figuring out a way to have the state from the checkbox affect the rendering of the sibling view component.
Normally the solution would be to just declare the state hook in the parent component and pass them down to each child, but since the siblings are rendered via foreach loops it's impossible to do so.
My current solution is to also generate the state hooks for each foo in a loop as well, but that feels a bit hacky since it's better to avoid creating hooks inside of loops (it's worth nothing that someArrayWithSeveralElements is not intended to change after mounting).
Is there a more elegant alternative to solve this?
The solution is what you side, you need to create a state in the parent component and pass it to the children. and this will work for single component or bunch of them, the difference is just simple: use array or object as state.
const [checkboxesStatus, setCheckboxesStatus] = useState({// fill initial data});
const children1 = someArrayWithSeveralElements.map(foo =>
<SomeView
visibile={checkBoxesStatus[foo.id]}
/>);
const children2 = someArrayWithSeveralElements.map(foo =>
<SomeCheckbox
checked={checkBoxesStatus[foo.id]}
onChange={// set new value to foo.id key}
/>)
export default function Parent() {
const [states, setStates] = React.useState([]);
const children1 = someArrayWithSeveralElements.map((foo, i) => <SomeView state={states[i]} />);
const children2 = someArrayWithSeveralElements.map((foo, i) => {
const onStateChange = (state) => {
setStates(oldStates => {
const newStates = [...(oldStates || [])]
newStates[i] = state;
return newStates;
})
}
return <SomeCheckbox state={states[i]} onStateChange={onStateChange} />;
});
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
Use states in the parent componet.
Note: the element of states may be undefined.

Set React Context inside function-only component

My goal is very simple. I am just looking to set my react context from within a reusable function-only (stateless?) react component.
When this reusable function gets called it will set the context (state inside) to values i provide. The problem is of course you can't import react inside a function-only component and hence I cannot set the context throughout my app.
There's nothing really to show its a simple problem.
But just in case:
<button onCLick={() => PlaySong()}></button>
export function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //cannot call useContext in this component
}
If i use a regular react component, i cannot call this function onClick:
export default function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //fine
}
But:
<button onCLick={() => <PlaySong />}></button> //not an executable function
One solution: I know i can easily solve this problem by simply creating a Playbtn component and place that in every song so it plays the song. The problem with this approach is that i am using a react-player library so i cannot place a Playbtn component in there...
You're so close! You just need to define the callback inside the function component.
export const PlaySongButton = ({...props}) => {
const {setCurrentSong} = useContext(StoreContext);
const playSong = () => {
setCurrentSong("some song");
}
return (
<button
{...props}
onClick={() => playSong()}
/>
)
}
If you want greater re-usability, you can create custom hooks to consume your context. Of course where you use these still has to follow the rules of hooks.
export const useSetCurrentSong = (song) => {
const {setCurrentSong} = useContext(StoreContext);
setCurrentSong(song);
}
It is possible to trigger a hook function by rendering a component, but you cannot call a component like you are trying to do.
const PlaySong = () => {
const {setCurrentSong} = useContext(StoreContext);
useEffect( () => {
setCurrentSong("some song");
}, []
}
return null;
}
const MyComponent = () => {
const [shouldPlay, setShouldPlay] = useState(false);
return (
<>
<button onClick={() => setShouldPlay(true)}>Play</button>
{shouldPlay && <PlaySong />}
</>
)
}

Reactjs hook that uses other hooks

I needed to create a custom hook which is supposed to contain all the handlers that will be used everywhere in my page. My requirements were;
Handlers are supposed to be accessible from all the components in the page
Handlers should be able to use other hooks, like useContext
So, created a useHandlers hook sandbox
However, couldn't make the LogHandler accessible from the page, receving LogHandler is not a function
Any idea?
The issue why you're getting LogHandler is not a function is because it's undefined and it doesn't get initialized until HandlerComp gets called:
export const userHandlers = (): IUseHandlers => {
// initialization skipped, so `LogHandler` is undefined
let LogHandler: () => void;
const HandlersComp: React.FunctionComponent<HandlersProps> = (
props: HandlersProps
) => {
// initialized here, but that's after `HandlersComp` gets caled
LogHandler = () => {
console.log("Hi from LogHandler");
};
return <></>;
};
return { HandlersComp, LogHandler };
}
I suggest you move the initialization step from HandlersComp like so:
export const useHandlers = (): IUseHandlers => {
const LogHandler: () => void = () => {
console.log("Hi from LogHandler");
};
const HandlersComp: React.FunctionComponent<HandlersProps> = (
props: HandlersProps
) => {
LogHandler()
return <></>;
};
return { HandlersComp, LogHandler };
};
Some notes:
HandlersComp looks like it should be a separate and reusable component, rather than a hook
LogHandler also looks more like a utility function, rather than a hook
LogHandler shouldn't be using PascalCase for naming as that should be "reserved" for React components; HandlersComp is fine, since it looks like it's a component

Is there a way to do an api call only once in react functional component?

Sorry if it's a beginner question>
I am trying to use Functional Component, as I was doing Class Component all the time.
I have a simple component that should load a list from a server, and display it.
The component looks like this (I simplified a bit so sorry if there is a type) :
const ItemRelationsList = (props: ItemRelationsListProps): JSX.Element => {
const [getList, setList] = useState([]);
const loadRelation = (): void => {
HttpService.GetAsync<getListRequest, getListResponse>('getList',{
// params
}).subscribe(res => {
setList(res.data.list);
});
}
loadRelation();
return (
<>
<Table
columns={columns}
dataSource={getList}
>
</Table>
</>
)
}
thew problem I face is that everytime I use setList, the component is redraw, so the http call is reexecute.
Is there a way to prevent that other than use a class component ?
use useEffect
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
const ItemRelationsList = (props: ItemRelationsListProps): JSX.Element => {
const [getList, setList] = useState([]);
// componentDidMount
useEffect(() => {
loadRelation()
}, [])
const loadRelation = (): void => {
HttpService.GetAsync<getListRequest, getListResponse>('getList',{
// params
}).subscribe(res => {
setList(res.data.list);
});
}
return (
<>
<Table
columns={columns}
dataSource={getList}
>
</Table>
</>
)
}
useEffect(yourCallback, []) - will trigger the callback only after the
first render.
Read the Docs hooks-effect
This is related to How to call loading function with React useEffect only once

Resources