Before I was doing this with local state but now I need to pass it to Redux. I am not using Redux Forms and I am not going to.
This is how I was/am doing it with local state using the useState hook:
const DynamicInputExample = () => {
const [addShareStructureInput, setAddShareStructureInput] = useState({
inputs: ['input-0'],
});
const appendInput = () => {
const newInput = `input-${addShareStructureInput.inputs.length}`;
setAddShareStructureInput(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
};
return(
<div id="dynamicInput">
// HERE I MAP THE INPUTS
{addShareStructureInput.inputs.map(input => (
<FormField
key={input}
onChange={() => onChange()}
/>
))}
<div>
<Button
type="button"
// HERE I CALL THE FUNCTION TO ADD NEW INPUT
onClick={() => appendInput()}
>
+ Add More
</Button>
</div>
</div>
)
}
But now I need to remove that hook on that code and make the same logic on Redux.
This is what I've done so far:
Action:
export const shareStructureInputsAction = shareStructureInputs => ({
type: ActionTypes.SHARE_STRUCTURE_INPUTS,
payload: { shareStructureInputs },
});
Reducer:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
// BELOW I NEED TO ADD THE LOGIC TO KEEP THE STATE OF THE INPUTS ADDED
shareStructureInputs: {
...action.payload.shareStructureInputs,
},
};
},
}
So, how can I simulate the same logic/behavior with Redux instead?
It's possible to do it, using something like this:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
shareStructureInputs: [...shareStructureInputs, action.payload.shareStructureInputs],
};
},
}
Action:
export const shareStructureInputsAction = shareStructureInputs => ({
type: ActionTypes.SHARE_STRUCTURE_INPUTS,
payload: shareStructureInputs
});
Reducer:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
shareStructureInputs: action.payload
};
},
}
import { connect } from 'react-redux';
import { shareStructureInputsAction } from 'actions/shareStructureInputsAction';
const DynamicInputExample = (props) => {
const { shareStructureInputs, setShareStructureInputs } = props;
const appendInput = () => {
const newInput = `input-${shareStructureInputs.length}`;
setShareStructureInputs(shareStructureInputs.concat(newInput));
};
return(
<div id="dynamicInput">
// HERE I MAP THE INPUTS
{shareStructureInputs.map(input => (
<FormField
key={input}
onChange={() => onChange()}
/>
))}
<div>
<Button
type="button"
// HERE I CALL THE FUNCTION TO ADD NEW INPUT
onClick={() => appendInput()}
>
+ Add More
</Button>
</div>
</div>
)
}
const mapStateToProps((state) => ({
shareStructureInputs: state[ActionTypes.SHARE_STRUCTURE_INPUTS].shareStructureInputs
}));
const mapDispatchToProps({
setShareStructureInputs: shareStructureInputsAction
})
export default connect(mapStateToProps, mapDispatchToProps)(DynamicInputExample);
Related
I have a situation here where there are two components. the first component have an input field and the other component is displaying icon and a title name. i used Redux toolkit where when the input field is written anything the icon is changed to another icon. the problem is the icon doesn't change i don't why so, i want to know how to define the icon in the component as shown in my code below.
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
icon: "unTick",
value: "",
};
const tickSlice = createSlice({
name: "tickSign",
initialState,
reducers: {
setValue: (state, action) => {
state.value = action.payload;
},
changeIcon: (state) => {
if (state.icon === "unTick") {
state.icon = "tick";
} else {
state.icon = "unTick";
}
},
},
});
export const { changeIcon, setValue } = tickSlice.actions;
export const selectValue = (state) => state.value;
export const selectIcon = (state) => state.icon;
export default tickSlice.reducer;
the code of the first component of input
const value = useSelector(selectValue);
const dispatch = useDispatch();
const handleChange = useCallback(
(e) => {
dispatch(setValue(e.target.value));
if (e.target.value.trim().length !== 0) {
dispatch(changeIcon("tick"));
} else {
dispatch(changeIcon("unTick"));
}
},
[dispatch]
);
<input
id="boxInput"
type="text"
value={value}
placeholder="Enter Apprenticeship Title"
onChange={handleChange}
/>
the code for the icon change component
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { selectIcon } from "../features/TickSlice";
import { TickCircle, UnTickCircle } from "./IconSvg";
const DescNavTitles = ({ title }) => {
const icon = useSelector(selectIcon);
const dispatch = useDispatch();
return (
<div className="navTitle">
<svg>{icon === "tick" ? <TickCircle /> : <UnTickCircle />}</svg>
<p className="navTitle__name">{title}</p>
</div>
);
};
export default DescNavTitles;
So, i don't know where exactly should i define the icon to be displayed and changed dynamically
components on the page
Redux tool when written
Redux tool when not written
You are not selecting the icon state correctly. Based on the screen captures showing the Redux state from the devtools your root state has two properties, appren and tick.
The Redux state:
{
appren: {
boxItems: [....],
title: "",
position: "",
...
},
tick: {
icon: "....",
value: "...."
},
}
I'm assuming it's this state.tick that is the tickSign slice you are working with. Recall that the useSelector hook callback is passed the entire Redux state object.
const icon = useSelector(state => state.tick.icon);
The selectValue and selectIcon selector functions need to access the correct path to access the expected state properties.
export const selectValue = (state) => state.tick.value;
export const selectIcon = (state) => state.tick.icon;
Additionally, the changeIcon action is only toggling the state value when dispatched, but it seems you want to conditionally set the value from an input element's onChange handler. I suggest the following refactor:
const initialState = {
isTicked: false,
value: "",
};
const tickSlice = createSlice({
name: "tickSign",
initialState,
reducers: {
setValue: (state, action) => {
state.value = action.payload;
},
changeIsTicked: (state, action) => {
state.isTicked = action.payload;
},
},
});
export const { changeIcon, setValue } = tickSlice.actions;
export const selectValue = (state) => state.tick.value;
export const selectIsTicked = (state) => state.tick.isTicked;
const value = useSelector(selectValue);
const dispatch = useDispatch();
const handleChange = useCallback((e) => {
dispatch(setValue(e.target.value));
dispatch(changeIsTicked(e.target.value.trim().length !== 0));
}, [dispatch]);
<input
id="boxInput"
type="text"
value={value}
placeholder="Enter Apprenticeship Title"
onChange={handleChange}
/>
const DescNavTitles = ({ title }) => {
const isTicked = useSelector(selectIsTicked);
return (
<div className="navTitle">
<svg>{isTicked ? <TickCircle /> : <UnTickCircle />}</svg>
<p className="navTitle__name">{title}</p>
</div>
);
};
The isTicked state is actually unnecessary though as it can be completely derived from the state.tick.value state value. You can create a selector function that computes this. Example:
const initialState = {
value: "",
};
const tickSlice = createSlice({
name: "tickSign",
initialState,
reducers: {
setValue: (state, action) => {
state.value = action.payload;
},
},
});
export const { changeIcon, setValue } = tickSlice.actions;
export const selectValue = (state) => state.tick.value;
export const selectIsTicked = (state) => !!state.tick.value.length;
const DescNavTitles = ({ title }) => {
const isTicked = useSelector(selectIsTicked);
return (
<div className="navTitle">
<svg>{selectIsTicked ? <TickCircle /> : <UnTickCircle />}</svg>
<p className="navTitle__name">{title}</p>
</div>
);
};
In the component's render method, you are using the useSelector hook to get the current icon value from the state and conditionally render either TickCircle or UnTickCircle component based on the icon value.
const icon = useSelector(selectIcon);
return (
<div className="navTitle">
<svg>{icon === "tick" ? <TickCircle /> : <UnTickCircle />}</svg>
<p className="navTitle__name">{title}</p>
</div>
);
Try this.
I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})
I'm learning react context and while developing a todo application using useContext, I'm facing an issue where on submitting one task, the same task gets added two times to an array. The output component would loop through this array and display the results. While debugging I observed that, although the submit of task add only one entry into the array, not sure why and how, the consumer component gets the array with duplicate entry. Please let me know, what I'm missing.
Here is my code of index file that maintains context
import { createContext, useReducer } from "react";
import ContextReducer, { initialState } from "./ContextReducer";
const taskContext = createContext();
const ContextProvider = (props) => {
const [state, dispatch] = useReducer(ContextReducer, initialState);
const setTaskInput = (taskInput) => {
dispatch({
type: "SET_TASKINPUT",
payload: taskInput,
});
};
const addTask = (task) => {
dispatch({
type: "ADD_TASK",
payload: task,
});
};
const deleteTask = (id) => {
dispatch({
type: "DELETE_TASK",
payload: id,
});
};
const todoContext = {
todo: state.todo,
taskInput: state.taskInput,
setTaskInput,
addTask,
deleteTask,
};
return (
<taskContext.Provider value={todoContext}>
{props.children}
</taskContext.Provider>
);
};
export { taskContext };
export default ContextProvider;
This is the code for reducer
const initialState = {
todo: [],
taskInput: "",
};
const ContextReducer = (state = initialState, action) => {
if (action.type === "SET_TASKINPUT") {
state.taskInput = action.payload;
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "ADD_TASK") {
state.todo = [...state.todo, action.payload];
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "DELETE_TASK") {
state.todo = state.todo.filter((todo) => todo.id !== action.payload);
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
return state;
};
export { initialState };
export default ContextReducer;
This is the code of output component or say, consumer component
import React, { Fragment, useContext } from "react";
import { taskContext } from "../../Context";
import styles from "./Content.module.css";
const Output = () => {
const { todo, deleteTask } = useContext(taskContext);
const deleteHandler = (e) => {
deleteTask(+e.target.parentElement.parentElement.id);
};
return (
<Fragment>
{todo.length > 0 && (
<div className={styles.outputDiv}>
<ul>
{todo.map((task) => {
return (
<li key={task.id} id={task.id}>
<div className={styles.row1}>{task.task}</div>
<div className={styles.row2}>
<button className={styles.edit}>Edit</button>
<button className={styles.delete} onClick={deleteHandler}>
Delete
</button>
</div>
</li>
);
})}
</ul>
</div>
)}
</Fragment>
);
};
export default Output;
I want to remove an object from an array onClick when I click on the delete button, I write some code in my redux and react as well and it is not working !!
My reducers
import { ActionsTypes } from "../Constant/ActionsTypes";
argument(state, action)
const initialState = {
events : [],
days : (""),
};
export const SetEventReducer = (state = initialState, action) => {
switch(action.type) {
case ActionsTypes.SET_EVENTS :
return {... state, events : action.payload};
default :
return state;
}
};
export const trackDaysReducer = (state= initialState, action) => {
switch(action.type) {
case ActionsTypes.TRACK_DAYS:
return {... state, days : action.payload}
default :
return state;
}
};
export const removeEventReducer = (state = initialState,action) => {
switch(action.type) {
case ActionsTypes.REMOVE_EVENT :
return {}
default :
return state;
}
};
Event array represents my state array
My Event Component who contains the button
import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { RemoveEvent } from '../Redux/Actions/ActionsEvents';
const Event = () => {
const events = useSelector((state)=> state.allEvents.events);
const removeEvents = useSelector((state)=> state.removeEvents);
const dispatch = useDispatch();
const removeEventHandler = () => {
dispatch(RemoveEvent({}))
}
return (
<section>
{events.map((singleEvent)=> {
const {title, id, day} = singleEvent;
return (
<article className="event-pop-up" key={id} >
<h1>The Name of your Event is <span>{title}</span></h1>
<button className="btn event"
onClick={removeEventHandler}>
Delete Event</button>
</article>
)
})}
</section>
)
}
export default Event;
RemoveEventAction
export const RemoveEvent = (id) => {
return {
type : ActionsTypes.REMOVE_EVENT,
};
};
This is the link to check out the app : https://boring-boyd-ecbeee.netlify.app/
What do you think? Thanks
Try this code :
in yours reducers :
export const removeEventReducer = (state = initialState,action) => {
switch(action.type) {
case ActionsTypes.REMOVE_EVENT :
return {... state, events : state.events.filter((event) => event.id !== action.payload)} // payload = id in this case
default :
return state;
}
then in your Event Component who contains the button :
import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { RemoveEvent} from '../Redux/Actions/ActionsEvents';
const Event = () => {
const events = useSelector((state)=> state.allEvents.events);
const removeEvents = useSelector((state)=> state.removeEvents);
const dispatch = useDispatch();
const removeEventHandler = (id) => {
dispatch(RemoveEvent(id))
}
return (
<section>
{events.map((singleEvent)=> {
const {title, id, day} = singleEvent;
return (
<article className="event-pop-up" key={id} >
<h1>The Name of your Event is <span>{title}</span></h1>
<button className="btn event"
onClick={()=> removeEventHandler(id)}>
Delete Event</button>
</article>
)
})}
</section>
)
}
export default Event;
then in your RemoveEvent action
export const RemoveEvent = (id) => {
return {
type : ActionsTypes.REMOVE_EVENT, payload: id
};
};
You can remove an event using it's id.
const removeEventHandler = (id) => {
dispatch(RemoveEvent(id))
}
return (
<section>
{events.map((singleEvent)=> {
const {title, id, day} = singleEvent;
return (
<article className="event-pop-up" key={id} >
<h1>The Name of your Event is <span>{title}</span></h1>
<button className="btn event"
onClick={() => removeEventHandler(id)}>
Delete Event</button>
</article>
)
})}
</section>
After passing this id to the reducer you can loop through the events array and remove this event from array and set the new state.
And in your reducers, you can use filter() method to remove a particular event having that id
export const removeEventReducer = (state = initialState, action) => {
switch(action.type) {
case ActionsTypes.REMOVE_EVENT :
return {... state, events : state.events.filter((event) => event.id !== action.payload)}
default :
return state;
}
}
For Remove Event Action:
export const RemoveEvent = (id) => {
return {
type : ActionsTypes.REMOVE_EVENT,
payload: id,
};
};
Wrote a React Hooks component that renders randomly selected quotes from Redux.
I'm struggling to read the quote from props in the first render (had to write a switch statement in JSX to read from the local React component state on first render instead).
All fine to read from props when one clicks on the button to get a new random quote (i.e. much after the first render). Guess props is too slow to update.
Pointers on how to deal with it most welcome. What I've done is more of a hack as I'm not reading from the Redux store at all on first render.
const defaultState = {};
const NEW_QUOTE = "NEW_QUOTE"
const newQuoteActionCreator = (quoteObject) => {
return {
type: NEW_QUOTE,
payload: quoteObject
};
};
const getNextQuoteReducer = (state = defaultState, action) => {
switch (action.type) {
case NEW_QUOTE:
return {
...state,
data: action.payload
};
default:
return state;
}
};
const store = Redux.createStore(getNextQuoteReducer);
const quotes = [
{
quoteText:"\"AAAAAA.\"",
quoteAuthor:"BBBB",
},
{
quoteText:"\"CCCCC.\"",
quoteAuthor:"DDDD",
}
];
React bit:
const QuoteBox = ({ text, author }) => { //destructuring
return (
<React.Fragment>
<div className="quotable-square">
<div className="content">
<div id="text">{text}</div>
<div id="author" className="author">{author}</div>
</div>
</div>
</React.Fragment>
)
}
const Button = ({ onClick, title }) => {
return (
<button className="new-quote" onClick={onClick}>{title}</button>
)
}
const App = (props) => {
const [quote, setQuote] = React.useState(() => {
const initialQuote = quotes[Math.floor(Math.random() * quotes.length)];
return {
data: initialQuote
}
});
const chosenRandomQuoteToState = () => {
let chosenQuote = randomQuoteFunction(quotes);
props.selectNewQuote(chosenQuote);
}
return (
<React.Fragment>
<div className="container">
<div id="quote-box">
<QuoteBox text={(() => {
switch (typeof props.currentQuote.data) {
case "undefined": return quote.data.quoteText
default: return props.currentQuote.data.quoteText;
}
})()}/>
<div className="actions">
<Button id="new-quote" title="Get New Quote" onClick={chosenRandomQuoteToState} />
</div>
</div>
</div>
</React.Fragment>
)
}
React Redux bit:
const Provider = ReactRedux.Provider;
const mapStateToProps = (state) => {
return {
currentQuote: state
}
const mapDispatchToProps = (dispatch) => {
return {
selectNewQuote: function(quoteToBeNewQuote) {
dispatch(newQuoteActionCreator(quoteToBeNewQuote));
}
}
}
const connect = ReactRedux.connect
const Container = connect(mapStateToProps, mapDispatchToProps)(App);
const AppWrapper = () => {
return (
<Provider store= {store}>
<Container />
</Provider>
);
};
ReactDOM.render(<AppWrapper />, document.getElementById('app'));
Newbie, so be easy on me :)
Try something like this:
const initialState = {
quotes: [
{
quoteText: '"AAAAAA."',
quoteAuthor: 'BBBB',
},
{
quoteText: '"CCCCC."',
quoteAuthor: 'DDDD',
},
],
}; //renamed defaultState
const mapStateToProps = state => {
//state should hold data, container (map state) should
// create values based on that data
const { quotes } = state;
return {
currentQuote:
quotes[Math.floor(Math.random() * quotes.length)],
};
};
const mapDispatchToProps = dispatch => ({
selectNewQuote: dispatch(newQuoteActionCreator()),
});
const getNextQuoteReducer = (
state = defaultState,
action
) => {
switch (action.type) {
case NEW_QUOTE:
return {
...state,
quotes: [...state.quotes], //forces app container to re run
};
default:
return state;
}
};
And get rid of useState in your components as well as all complex logic with your action, the action only needs a type and your reducer will re refernce quites so App container will re run and pick another random quote.
Rather than messing with extra state in your component, you should go all-in with letting redux manage the state of the current quote. Since you want a random quote selected in your store at the very start, you could pre-select a random quote selection in your store's initial state or dispatch the selection of a random quote as an effect so it happens on mount. Here's the latter approach:
const App = (props) => {
useEffect(() => {
chosenRandomQuoteToState();
}, []);
const chosenRandomQuoteToState = () => {
let chosenQuote = randomQuoteFunction(quotes);
props.selectNewQuote(chosenQuote);
};
const currentQuote = props.currentQuote.data;
return (
<React.Fragment>
<div className="container">
<div id="quote-box">
{currentQuote && <QuoteBox text={currentQuote}/>}
<div className="actions">
<Button id="new-quote" title="Get New Quote" onClick={chosenRandomQuoteToState} />
</div>
</div>
</div>
</React.Fragment>
)
}