Cannot be used as a JSX component. Its return type 'Promise<Element>' is not a valid JSX element - promise

I have an React Component Editor. I am trying to initialize the state using an async function. But I am unable to .
How we can do that in React.
const Editor = () => {
const { id } = useParams();
const [schemas, updateSchemas] = useAtom(bfsAtom);
const schema = id && _.get(schemas, id, {});
type InitialStateType = {
properties: KeyedProperty[];
validations: ValidationDataProperty[];
};
const getInitialState = async (): Promise<InitialStateType> => {
return {
properties: createPropertiesFromSchema(schema),
validations: initializeConditions(schema),
};
};
const initialState = await getInitialState();
const mainReducer = (
{ properties, validations }: InitialStateType,
action: Action
) => ({
properties: propertyReducer(properties, action),
validations: validationReducer(validations, action),
});
const [state, dispatch] = useReducer(mainReducer, initialState);
return (
<PropertyContext.Provider value={{ state, dispatch }}>
<SchemaEditor schema={schema} />
</PropertyContext.Provider>
);
};

TL;DR moving the async from top level to inside the component did the trick
Is not directly linked with your question but one thing that I was doing wrong is the following:
I had a component that looked like:
const MyComponent = async () => {
...
const apiResponse = await apiFunction();
return <div>
{apiResponse.success && (
<p>It was a success!</p>
)}
</div>
}
BUT you shouldn't use async in your components at top level. So to fix it I did this:
const MyComponent = () => {
...
const apiResponse = async () => {
return await apiFunction();
}
return <div>
{apiResponse().success && (
<p>It was a success!</p>
)}
</div>
}

Related

useEffect not triggered by prop dependency

So I have this component set up, which has an onClick handler to like or unlike a certain recipe. I am using the useEffect hook to make sure that the icon is changed accordingly based on the favoriteId prop. When the onClick and the associated queries are executed however, the useEffect hook is not triggered at all, how come?
const RecipeCard = ({ name, image, id, favoriteId }) => {
const { user } = useContext(AuthenticatedUserContext);
const [isFavorite, setIsFavorite] = useState(false);
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
favoriteId = res.id;
}
};
useEffect(() => {
console.log("hit");
favoriteId ? setIsFavorite(true) : setIsFavorite(false);
}, [favoriteId]);
return (
<TouchableWithoutFeedback
onPress={onPress}
style={{ flex: 1, padding: 10 }}
>
<View>
<AntDesign
onPress={() => {
if (!user) {
setShowNoAccountModal(true);
} else {
onLikePress();
}
}}
name={isFavorite ? "like1" : "like2"}
color="black"
size={30}
/>
</View>
</TouchableWithoutFeedback>
);
};
export default RecipeCard;
Parent component:
export const HomeScreen = () => {
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
getRecipes();
}, []);
const getRecipes = async () => {
const querySnapshot = await getDocs(collection(db, "receipes"));
const fetchedRecipes = [];
for (const d of querySnapshot.docs) {
const citiesRef = collection(db, "favorites");
const q = query(
citiesRef,
where("userId", "==", user.uid),
where("recipeId", "==", d.id)
);
const querySnapshot = await getDocs(q);
const isFavorite = false;
if (querySnapshot.empty) {
favoriteId = null;
} else {
favoriteId = querySnapshot.docs[0].id;
}
const recipe = {
...d.data(),
id: d.id,
favoriteId,
};
fetchedRecipes.push(recipe);
}
setRecipes(fetchedRecipes);
setLoading(false);
};
return (
<View style={styles.container}>
{/* <Button title="Sign Out" onPress={handleLogout} /> */}
<Text style={{ fontSize: 24, fontWeight: "bold", paddingBottom: 10 }}>
Recepten
</Text>
{recipes && recipes.length > 0 && (
<FlatList
data={recipes}
renderItem={({ item }) => (
<RecipeCard
name={item.title}
id={item.id}
image={item.thumbnail}
favoriteId={favoriteId}
/>
)}
keyExtractor={(item) => item.id}
horizontal
/>
)}
</View>
);
};
You are mutating your favoriteId variable, but not using setState, so it is not done properly and react is unaware your variable might have changed.
To fix this, you will need to pass a function to change your favoriteId prop inside of your component's parent:
// in parent:
const [favoriteId, setFavoriteId] = useState() // this code should be here already
return (
// your code here
<RecipeCard changeFavoriteId={(newId) => setFavoriteId(newId)} />
// just add this changeFavoriteId prop, the old props should be here still though
// rest of your code
)
// in RecipeCard.js
const RecipeCard = ({ name, image, id, favoriteId, changeFavoriteId }) => {
// your code here
const onLikePress = async () => {
if (favoriteId) {
await deleteDoc(doc(db, "favorites", favoriteId));
favoriteId = null;
} else {
const res = await addDoc(collection(db, "favorites"), {
userId: user.uid,
recipeId: id,
});
changeFavoriteId(res.id) // this bit here changed, now you are using setState
}
};
// the rest of your code
By using setState function that you obtained from parent useState, your component will trigger a rerender after the value is changed.

How to make a rxjs subscription after a specific action in react hooks?

As we all know that we need to make a subscription in useeffect and unsubscribe it when the component will unmount. But this kind of code will be triggered once the component is mounted. I'm now want to trigger the subscription after a specific action.Look at the code below.
const [timing, setTiming] = useState<number>(60)
const interval$ = interval(1000)
useEffect(() => {
})
const sendCodeOnceSubmit = async (phone: number) => {
const res = await sendCode(phone)
if (res.code !== 200) {
message.error(`${res.message}`)
} else {
interval$.pipe(take(60)).subscribe(() => setTiming(timing - 1))
}
}
I have a form in the dom,and once I click submit,the sendCodeOnceSubmit function will be triggered which will then send a request through sendCode function to the server. Once the server return a success code, I want to make a countdown with rxjs, but how can I unsubscribe it cause the normal way to do it is to subscribe a observable in useeffect. Thanks for anyone who can help.
Just wrap interval$ with useState and write a useEffect for it.
// Moved const out of component.
const defaultTiming = 60;
/* ... */
export default function App() {
const [timing, setTiming] = useState<number>(defaultTiming);
const [interval$, setInterval$] = useState<Observable<number> | undefined>();
useEffect(() => {
if (!interval$) return;
const subscription = interval$.pipe(take(defaultTiming)).subscribe(() => {
setTiming((prev) => prev - 1);
});
return () => subscription.unsubscribe();
}, [interval$]);
const sendCodeOnceSubmit = async (phone: number) => {
const res = await sendCode(phone);
if (res.code !== 200) {
// message.error(`${res.message}`);
console.error(res.message);
} else {
setInterval$(interval(1000));
}
};
return (
<div className="App">
<p>{timing}</p>
<button type="button" onClick={() => sendCodeOnceSubmit(123)}>
Click
</button>
</div>
);
}

React-Redux Maximum call stack size exceeded when adding object to list

I am creating a simple game react app and when I try to add a player to my players list it seems to be creating an infinite loop and I'm not sure why. I tried to use useEffect to render the player list on initial load but that didn't help so I removed it for now to simplify. Any ideas what I could be doing differently?
App.js
import React, { useEffect } from 'react'
import {useDispatch, useSelector} from 'react-redux';
import './App.css';
import {setPlayerName, increaseCurrentPlayerId, decreaseCurrentPlayerId, addPlayerToList} from './redux/reducers/playerReducer';
function App() {
const dispatch = useDispatch()
const playerName = useSelector(state => state.playerName);
const playerList = useSelector(state => state.playerList);
const currentPlayerId = useSelector(state => state.currentPlayerId)
// dispatch(addPlayerToList('Test'))
const addPlayer = (player) => {
dispatch(addPlayer(player))
dispatch(setPlayerName(''))
}
const renderPlayerList = () => {
if (playerList.length < 1) {
return (
<div>
No Players
</div>
)
} else {
return (
playerList.map(p =>
<p>p.name</p>
)
)
}
}
return (
<div className="App">
<input
type='text'
name='playerName'
onChange={({ target }) => dispatch(setPlayerName(target.value))}
required
/>
Name<br/>
<button type='button'
onClick={() => addPlayer(playerName)}
>
Add Player</button> <br />
<br />
</div>
);
}
export default App;
playerReducer.js
export const playerNameReducer = (state = '', action) => {
switch (action.type) {
case 'SET_PLAYER_NAME':
return action.data;
default:
return state;
}
};
export const playerListReducer = (state = null, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state, action.data];
default:
return state;
}
};
Action Creators
export const setPlayerName = playerName => {
return {
type: 'SET_PLAYER_NAME',
data: playerName,
};
};
export const addPlayerToList = player => {
return {
type: 'ADD_PLAYER',
data: player,
};
};
addPlayer calls itself
const addPlayer = (player) => {
dispatch(addPlayer(player))
}

Dispatching Redux Thunks to re-render React Component

I'm trying to implement a sports betting filter, but am getting stuck when trying to dispatch a thunk to re-render the page based on a sport-specific key that I pass into the thunk. I have a dropdown list that has click listeners that dispatch a handler in the parent component (GamesList). I'm noticing my action creator (gotKey) keeps returning the default key('americanfootball_ncaaf') even when clicking on the NFL list item in the DropdownList component. Any help or direction is much appreciated. Thanks!
Redux Store
import axios from 'axios'
require('../../secrets')
const GOT_GAMES = 'GOT_GAMES'
const GOT_MONEYLINES = 'GOT_MONEYLINES'
const GOT_KEY = 'GOT_KEY'
const gotGames = games => ({type: GOT_GAMES, games})
const gotMoneyLines = games => ({type: GOT_MONEYLINES, games})
const gotKey = key => ({type: GOT_KEY, key})
const defaultSportKey = 'americanfootball_ncaaf'
const apiKey = process.env.API_KEY
export const getGames = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}&region=us&mkt=spreads`
)
dispatch(gotGames(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
export const getMoneyLines = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}&region=us&mkt=h2h`
)
dispatch(gotMoneyLines(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
const gamesState = {
spreadGames: [],
moneylineGames: [],
sportKey: ''
}
const gamesReducer = (state = gamesState, action) => {
// console.log('action' + action)
switch (action.type) {
case GOT_GAMES:
return {...state, spreadGames: action.games}
case GOT_MONEYLINES:
return {...state, moneylineGames: action.games}
case GOT_KEY:
return {...state, sportKey: action.key}
default:
return state
}
}
export default gamesReducer
Main GamesList component
import React from 'react'
import {connect} from 'react-redux'
import {getGames, getMoneyLines} from '../store/games'
import SingleGame from './SingleGame'
import DropdownList from './DropdownList.js'
class GamesList extends React.Component {
constructor() {
super()
this.handler = this.handler.bind(this)
}
componentDidMount() {
this.props.getGames()
this.props.getMoneyLines()
}
handler(key) {
this.props.getGames(key)
this.props.getMoneyLines(key)
}
render() {
// console.log(this.state)
const spreadList = this.props.spreadGames.data
const moneylineList = this.props.moneylineGames.data
if (!spreadList || !moneylineList) {
return <div>loading</div>
} else {
return (
<div>
<DropdownList handler={this.handler} />
{spreadList.map((game, index) => (
(Date.now().toString().slice(0, -3) < game.commence_time) ?
<div key={index}>
<SingleGame
spreads={game.sites[0]}
homeTeam={game.home_team}
teams={game.teams}
timeStart={game.commence_time}
moneylineGame={moneylineList[index]}
/>
</div> : null
))}
</div>
)
}
}
}
const mapStateToProps = state => ({
spreadGames: state.games.spreadGames,
moneylineGames: state.games.moneylineGames,
sportKey: state.games.sportKey
})
const mapDispatchToProps = dispatch => ({
getGames: () => dispatch(getGames()),
getMoneyLines: () => dispatch(getMoneyLines())
})
export default connect(mapStateToProps, mapDispatchToProps)(GamesList)
DropdownList Component
import React from 'react'
export default class DropdownList extends React.Component {
constructor(props) {
super()
this.state = {
listOpen: false
}
this.showList = this.showList.bind(this)
this.hideList = this.showList.bind(this)
}
showList(event) {
event.preventDefault()
this.setState({listOpen: true}, () => {
document.addEventListener('click', this.hideList)
})
}
hideList() {
this.setState({listOpen: false}, () => {
document.addEventListener('click', this.hideList)
})
}
render() {
return (
<div>
<div type="button" onClick={this.showList}>
Select A Sport
</div>
{this.state.listOpen ? (
<ul>
<li
onClick={() => {
this.props.handler('americanfootball_nfl')
}}
>
NFL
</li>
<li
onClick={() => {
this.props.handler('americanfootball_ncaaf')
}}
>
NCAA
</li>
</ul>
) : (
<li>test</li>
)}
</div>
)
}
}
You're not passing your key in mapDispatchToProps
const mapDispatchToProps = dispatch => ({
getGames: key => dispatch(getGames(key)), // <-- forward key
getMoneyLines: key => dispatch(getMoneyLines(key)) // <-- forward key
})
react-redux has a nicer api for this, you just pass your action creators directly
export default connect(mapStateToProps, { getGames, getMoneyLines })(GamesList)
https://react-redux.js.org/api/connect#example-usage

HOC is triggering while I use it on container

Could anyone can help me, because i can't understand why HOC is causing ifnite loop while I am using it on container. That's my container:
class UserContainer extends Component {
componentDidMount() {
const { onValuePassedThroughParams, match } = this.props;
const { user } = match.params;
if (user !== '') {
onValuePassedThroughParams(user);
}
}
render() {
const { user } = this.props;
return (
<UserView user={user} />
);
}
}
const UserContainerWithLoading = LoaderHOC(UserContainer);
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
onValuePassedThroughParams: val => dispatch(takeUserNameAndFetchData(val)),
});
export default
withRouter(
connect(mapStateToProps, mapDispatchToProps)(UserContainerWithLoading),
);
my HOC:
const LoaderHOC = WrappedComponent => props =>{
return(
props.user.isLoading
? <div className={styles.ldsHourglass} />
: <WrappedComponent {...props} />
)};
and also a thunk:
function fetchData(url) {
return (
fetch(url)
.then(result => result.json())
);
}
export default function takeUserNameAndFetchData(name) {
const userInfoUrl = `https://api.github.com/users/${name}`;
const userRepoUrl = `https://api.github.com/users/${name}/repos`;
return (dispatch) => {
dispatch(fetchUserBegin());
Promise.all([
fetchData(userInfoUrl),
fetchData(userRepoUrl),
])
.then(([info, repos]) => {
dispatch(fetchUserInfoSucces(info));
dispatch(fetchUserReposSuccess(repos));
dispatch(fetchUserLoadingEnd());
})
.catch((err) => {
console.log(`ERROR!${err}`);
dispatch(fetchUserError());
});
};
}
When I use an HOC on my View component everything is fine and it's stop geting data from the server, but when I am using it on container there is always an infinite loop. Do you have any advice for me?

Resources